package mc.support.task.mgr;

import mc.support.task.common.Controllable;
import mc.support.task.common.MultiRunnable;
import it.sauronsoftware.cron4j.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created with IntelliJ IDEA. <br/>
 * Author: Francis Yun    <br/>
 * Date: 2014-04-15  <br/>
 */
public class TaskExecutor {

    private static Logger log = LoggerFactory.getLogger(TaskExecutor.class);

    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private static final ExecutorService exec = Executors.newCachedThreadPool(
            new ThreadFactory() {
                private final ThreadGroup group;
                private final AtomicInteger threadNumber = new AtomicInteger(1);
                private final String namePrefix;

                {
                    SecurityManager s = System.getSecurityManager();
                    group = (s != null) ? s.getThreadGroup() :
                            Thread.currentThread().getThreadGroup();
                    namePrefix = "ming-task-worker-pool-" +
                            poolNumber.getAndIncrement() +
                            "-thread-";
                }

                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(group, r,
                            namePrefix + threadNumber.getAndIncrement(),
                            0);

                    t.setDaemon(true);
                    if ( t.getPriority() != Thread.NORM_PRIORITY ) {
                        t.setPriority(Thread.NORM_PRIORITY);
                    }
                    t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                        @Override
                        public void uncaughtException(Thread t, Throwable e) {
                            if ( log.isDebugEnabled() ) {
                                e.printStackTrace();
                            }
                            log.error( "Uncaught Exception Detected: {} in Thread: {} ", new Object[] { e, t.getName() } );
                        }
                    });
                    return t;
                }

            }
    );

    private static final Scheduler scheduler = new Scheduler();

    private Set<Controllable> controllables = new HashSet<>();

    public synchronized void submit(Runnable r) {
        if ( isMultiRunnable( r ) ) {
            doMultiLaunch( (MultiRunnable) r);
        } else {
            submitAndRegister( r );
        }
    }

    private void submitAndRegister(Runnable r) {
        registerControllable( r );
        exec.submit( r );
    }

    public synchronized void schedule(String pat, Runnable r) {
        registerControllable( r );
        scheduler.schedule( pat, r );
    }

    public synchronized void startScheduler() {
        scheduler.setDaemon( true );
        scheduler.start();
    }

    public synchronized void close() {

        stopControllables();
        exec.shutdown();

        if ( scheduler.isStarted() ) {  //in some case scheduler won't start successfully,
                                        // so do checking before stopping
            scheduler.stop();

        }

    }

    private void stopControllables() {
        if ( ! controllables.isEmpty() ) {
            for ( Controllable c : controllables ) {
                try {
                    c.setKeepOn( false );
                } catch (Exception e) {
                    log.error( " close controllable error: {} ", e );
                }
            }
        }
    }

    private void registerControllable( Runnable c ) {
        if ( c instanceof Controllable ) {
            controllables.add( (Controllable) c );
        }
    }

    private boolean isMultiRunnable(Runnable r) {
        return null != r && r instanceof MultiRunnable;
    }

    private void doMultiLaunch(MultiRunnable mt) {
        int insCount = mt.instanceCount();
        for ( int i = 0; i < insCount; i++ ) {
            Runnable rr = mt.copySelf();
            submitAndRegister( rr );
        }
    }

}
